﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using HtmlAgilityPack;
using System.Net;
#endregion

namespace WpfFootball.Parser
{
    /// <summary>
    /// Contains functions that allows the application to parse information
    /// from a given website of a football-team.
    /// </summary>
    public class DataParser
    {
        #region Delegates and events
        public delegate void ParseForumEntriesEventHandler(object sender, ForumEntriesEventArgs args);
        public delegate void ParseFixturesEventHandler(object sender, FixturesEventArgs args);
        public delegate void ParseNewsEntriesEventHandler(object sender, NewsEntriesEventArgs args);
        public delegate void ParseHtmlTagsEventHandler(object sender, ParseHtmlEventArgs args);
        public delegate void ParseNextTeamGameEventHandler(object sender, NextGameEntry arg);
        public delegate void ParseOperation(ParseHtmlArgs args);

        public event ParseForumEntriesEventHandler ParseForumEntriesCompleted;
        public event ParseFixturesEventHandler ParseFixturesEntriesCompleted;
        public event ParseNewsEntriesEventHandler ParseNewsEntriesCompleted;
        public event ParseNextTeamGameEventHandler ParseNextTeamGameCompleted;
        public event ParseHtmlTagsEventHandler ParseHtmlTagsCompleted;
        #endregion

        #region Fields
        private IOManager ioManager;
        #endregion

        public DataParser()
        {
            ioManager = IOManager.Instance();
            RegisterEventHandlers();
        }

        private void RegisterEventHandlers()
        {
            ParseHtmlTagsCompleted += parser_ParseHtmlTagsCompleted;
        }

        #region Forum Entry Parser
        public void BeginParseForumEntries(string teamName, int numberOfEntries)
        {
            string teamForumSourceURL = ioManager.GetTeamForumSource(teamName);
            ParseHtmlArgs args = new ParseHtmlArgs(teamForumSourceURL, "div", numberOfEntries, new ParseOperation(ParseForumEntries), null);
            ParseHtmlTagsAsync(args);
        }

        private void ParseForumEntries(ParseHtmlArgs args)
        {
            List<ForumEntry> forumEntries = new List<ForumEntry>();
            HtmlNodeCollection nodes = args.parsedNodes;

            try
            {
                if (nodes.Count > 0)
                {
                    int currentEntry = 0;
                    int readEntries = 0;

                    while (readEntries <= args.counter)
                    {
                        if (!nodes[currentEntry].OuterHtml.Contains("FONT-SIZE: 10pt") &&
                            !nodes[currentEntry].OuterHtml.Contains("main_container") &&
                            !nodes[currentEntry].OuterHtml.Contains("center_column_container") &&
                            nodes[currentEntry].OuterHtml.Contains("m_cont"))
                        {
                            HtmlNode currentElem = nodes[currentEntry];
                            ForumEntry forumEntry = new ForumEntry();
                            HtmlNodeCollection forumEntryDetails = GetNodesFromNode(currentElem, "b");
                            forumEntry.TimeStamp = forumEntryDetails[1].InnerText.Trim();
                            forumEntry.Sender = forumEntryDetails[0].InnerText.Trim();
                            forumEntry.EntryContent = GetNodesFromNode(currentElem, "div")[0].InnerText.TrimStart().TrimEnd();
                            forumEntries.Add(forumEntry);
                            readEntries++;
                            currentEntry++;

                            continue;
                        }
                        currentEntry++;
                    }

                    if (ParseForumEntriesCompleted != null)
                    {
                        ParseForumEntriesCompleted(this, new ForumEntriesEventArgs(forumEntries));
                    }
                }

            }
            catch (Exception ex)
            {
                throw new NullReferenceException("An Exception occured while parsing forum entries", ex);
            }
        }
        #endregion

        #region Fixtures Entry Parser
        public void BeginParseNextGames(string teamName, int numberOfGames)
        {
            string teamSourceURL = ioManager.GetTeamNewsSource(teamName);
            ParseHtmlArgs args = new ParseHtmlArgs(teamSourceURL, "div", numberOfGames, new ParseOperation(ParseNextGames), null);
            ParseHtmlTagsAsync(args);
        }

        private void ParseNextGames(ParseHtmlArgs args)
        {
            List<NextGameEntry> nextGameEntries = new List<NextGameEntry>();
            HtmlNodeCollection nodes = args.parsedNodes;
            int readGames = 0;

            foreach (HtmlNode htmlElement in nodes)
            {
                if (!htmlElement.OuterHtml.Contains("main") &&
                    !htmlElement.OuterHtml.Contains("resultsModule") &&
                    !htmlElement.OuterHtml.Contains("right_column") &&
                    !htmlElement.OuterHtml.Contains("resultsContent") &&
                    htmlElement.OuterHtml.Contains("nextTab_pane"))
                {
                    HtmlNodeCollection nextGameBlocks = GetNodesFromNode(htmlElement, "tr");

                    foreach (HtmlNode gameBlock in nextGameBlocks)
                    {
                        if (readGames >= args.counter)
                        {
                            break;
                        }

                        NextGameEntry nextGameEntry = new NextGameEntry();
                        HtmlNodeCollection gameNodes = GetNodesFromNode(gameBlock, "td");
                        string[] playingTeams = gameNodes[1].InnerText.Trim().Split('-');
                        nextGameEntry.teamA = playingTeams[0];
                        nextGameEntry.teamB = playingTeams[1];
                        nextGameEntry.timeStamp = gameNodes[2].InnerText.Trim();
                        nextGameEntries.Add(nextGameEntry);

                        readGames++;
                    }
                    break;
                }
            }

            if (ParseFixturesEntriesCompleted != null)
            {
                ParseFixturesEntriesCompleted(this, new FixturesEventArgs(nextGameEntries));
            }
        }
        #endregion

        #region Team News Parser
        public void BeginParseTeamNews(string teamName, int numberOfNews)
        {
            string teamSourceURL = ioManager.GetTeamNewsSource(teamName);
            ParseHtmlArgs args = new ParseHtmlArgs(teamSourceURL, "li", numberOfNews, new ParseOperation(ParseTeamNews), null);
            ParseHtmlTagsAsync(args);
        }

        private void ParseTeamNews(ParseHtmlArgs args)
        {
            HtmlNodeCollection nodes = args.parsedNodes;
            List<NewsEntry> teamNews = new List<NewsEntry>();

            try
            {
                if (nodes.Count > 0)
                {
                    int currentEntry = 0;
                    int readEntries = 0;

                    while (readEntries < args.counter)
                    {
                        if (nodes[currentEntry].OuterHtml.Contains("feedItem"))
                        {
                            HtmlNode currentElem = nodes[currentEntry];
                            NewsEntry newsEntry = new NewsEntry();
                            HtmlNodeCollection newsNodes = GetNodesFromNode(currentElem, "div");
                            newsEntry.TimeStamp = newsNodes[0].InnerText.Trim();
                            newsEntry.HeadLine = newsNodes[1].InnerText.Trim();
                            string link = GetNodesFromNode(newsNodes[1], "a")[0].GetAttributeValue("href", string.Empty);
                            newsEntry.NewsContent = "http://www.goal.com" + link;
                            teamNews.Add(newsEntry);
                            readEntries++;
                            currentEntry++;

                            continue;
                        }
                        currentEntry++;
                    }
                }

                if (ParseNewsEntriesCompleted != null)
                {
                    ParseNewsEntriesCompleted(this, new NewsEntriesEventArgs(teamNews));
                }
            }
            catch (Exception ex)
            {
                throw new NullReferenceException("An Exception occured while parsing news entries", ex);
            }
        }
        #endregion

        public void BeginParseTeamNextGame(string teamName)
        {
            string teamSourceURL = ioManager.GetTeamNewsSource(teamName);
            ParseHtmlTagsAsync(new ParseHtmlArgs(teamSourceURL, "td", 0, new ParseOperation(ParseTeamNextGame), null));
        }

        public void ParseTeamNextGame(ParseHtmlArgs args)
        {
            HtmlNodeCollection nodes = args.parsedNodes;
            NextGameEntry nextGameEntry = new NextGameEntry();

            foreach (HtmlNode htmlElement in nodes)
            {
                if (htmlElement.OuterHtml.Contains("next_game_content"))
                {                    
                    nextGameEntry.timeStamp = GetNodesFromNode(htmlElement, "div")[1].InnerText.Trim();
                    nextGameEntry.teamA = GetNodesFromNode(htmlElement, "a")[2].InnerText.Trim();
                    nextGameEntry.teamB = GetNodesFromNode(htmlElement, "a")[6].InnerText.Trim();
                }
            }

            if (ParseNextTeamGameCompleted != null)
            {
                ParseNextTeamGameCompleted(this, nextGameEntry);
            }
            
        }

        #region Event Handling
        /// <summary>
        /// Called every time the html tag parsing process is done.
        /// </summary>
        public void parser_ParseHtmlTagsCompleted(object sender, ParseHtmlEventArgs e)
        {
            //Call the function that requested the html tags.
            e.args.parseOperation(e.args);
        }

        /// <summary>
        /// Called every time a WebClient has completely downloaded the html code. 
        /// </summary>
        /// <param name="sender">Event sender.</param>
        /// <param name="e">Object containing aguments sent by the triggered event.</param>
        public void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (!e.Cancelled && e.Error == null)
            {
                string htmlCode = e.Result;
                HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
                htmlDoc.LoadHtml(htmlCode);
                ParseHtmlArgs parseHtmlArgs = e.UserState as ParseHtmlArgs;
                HtmlNodeCollection nodes = htmlDoc.DocumentNode.SelectNodes(".//" + parseHtmlArgs.tagToParse);
                parseHtmlArgs.parsedNodes = nodes;

                //Fire an event indicating that the html parsing is completed.
                if (ParseHtmlTagsCompleted != null)
                {
                    ParseHtmlTagsCompleted(this, new ParseHtmlEventArgs(nodes, parseHtmlArgs));
                }
            }
        }
        #endregion

        /// <summary>
        /// Exracts all nodes from a node that matches the name of the nodes to select.
        /// </summary>
        /// <param name="node">The html to get the nodes from.</param>
        /// <param name="nodeToSelect">The name of the nodes to select.</param>
        /// <returns>A HtmlNodeCollections containg all matched nodes.</returns>
        public HtmlNodeCollection GetNodesFromNode(HtmlNode node, string nodeToSelect)
        {
            string xPath = ".//" + nodeToSelect;
            return node.SelectNodes(xPath);
        }

        /// <summary>
        /// Begins the html tag parsing process asynchronously.
        /// </summary>
        /// <param name="args">Object that contains all necesary values to be used during the parsing process.</param>
        public void ParseHtmlTagsAsync(ParseHtmlArgs args)
        {
            WebClient client = new WebClient();
            client.Encoding = Encoding.UTF8;
            client.DownloadStringCompleted += client_DownloadStringCompleted;
            client.DownloadStringAsync(new Uri(args.url), args);
        }
    }
}
